/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.management;

import com.vii.brillien.core.component.SuperFlow;
import com.vii.brillien.core.management.db.TransactionServices;
import com.vii.brillien.core.management.db.couchdbDocument.IndispensableFlow;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.atomic.Flow;

import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;

/**
 * Service class handling all Flow-related activities of Brillien application server
 */
public class FlowServices {

    /**
     * List of running flowa
     */
    public static ConcurrentLinkedQueue<Flow>                   flows;


    static{
        flows = new ConcurrentLinkedQueue<Flow>();
    }

    public static Collection<Flow> getFlows() {
        return flows;
    }

    /**
     * Passivates all inactive (terminated or suspended) flow instances
     */
    public static synchronized void passivateInactiveFlows() {
        LinkedList<Flow> inactiveFlows = new LinkedList<Flow>();
        for( Flow flow : flows ){
            if( !flow.equals( BrillienServices.SUPREME_FLOW ) && !flow.equals( BrillienServices.SERVICES_FLOW ) ){
                boolean waiting = flow.isUnitWaitingForResponse();
                boolean exit = flow.isUnitInExitState();
                boolean valid = flow.isUnitInValidStates();
                boolean suspended = flow.isSuspended();
                if(
                    (!waiting && (exit || !valid)) || suspended
                ){
                    inactiveFlows.add( flow );
                }
            }
        }
        for( Flow flow : inactiveFlows ){
            try{
                flow.passivateUnitAll();
                flow.retrieveUnit();
            } catch( Exception e){
                BrillienContext.exceptionLog( "ReferenceRetriever", "innerRun", e );
            }
        }
    }

    /**
     * Getter method for a flow with a given ID
     */
    public static synchronized Flow getFlow( String flowID ) throws BrillienException {
        for( Flow flow : flows ){
            if( flow.getFlowID().equals( flowID ) ){
                if( flow.isSuspended() ){
                    BrillienContext.systemLog( Level.FINE, "Flow reactivated:" + flow.getFlowID() );
                    flow.revivified();
                }
                return flow;
            }
        }

        IndispensableFlow suspendedFlow = CouchDBServices.getSuspendedFlowStateSpaceByID( flowID );
        if( suspendedFlow != null ){
            BrillienContext.systemLog( Level.FINE, "Getting a new Flow instance for ID: " + flowID );
            Flow f = BrillienServices.getPresenceByName( BrillienServices.SERVICES_FLOW, suspendedFlow.getFlowName() );
            BrillienContext.systemLog( Level.FINE, "Setting flowID and activating: " + flowID );
            f.setFlowID( flowID );
            f.setStateSpace( suspendedFlow.getFlowStateSpace() );
            f.revivified();

            BrillienContext.systemLog( Level.FINE, "Flow revivified:" + f.getName() );

            CouchDBServices.reportFlowReactivation( flowID );
            return f;
        }

        return null;
    }

    /**
     * Registers a new flow instance
     * @param flow flow to be registered
     * @return flow registered
     */
    public static Flow registerFlow( Flow flow ){
        BrillienContext.systemLog( Level.FINE, "Flow registered:" + flow.getName() );
        if( flow != null ){
            if( !flows.contains( flow ) ){
                flows.add( flow );
                
                BrillienContext.systemLog( Level.FINE, "New Flow registered::" + flow );
            }
        }

        return flow;
    }

    /**
     * Notify the system about the starting of a flow
     * @param flow flow to be started
     * @param communication starting communication of the given Flow instance
     */
    public static void flowStarted( Flow flow, long timeout, BrillienCommunication communication ) throws BrillienException {
        BrillienContext.systemLog( Level.FINE, "Flow started:" + flow.getName(), communication );
        if( flow.isIndispensable() ){
            try {
                CouchDBServices.flowStarted(
                        flow.getFlowID(),
                        flow.getName(), flow.getStateSpace(),
                        timeout,
                        communication
                );
            } catch (IOException e) {
                throw new BrillienException( e.getMessage(), e );  
            }
        }
    }

    /**
     * Notify the system about the state change event occured in a flow instance
     */
    public static void flowChagedState( Flow flow ) throws BrillienException {
        BrillienContext.systemLog( Level.FINE, "Flow state changed:" + flow.getName() + " " + flow.getFlowID() );
        if( flow.isIndispensable() ){
            CouchDBServices.reportChangedState( flow.getFlowID(), flow.getStateSpace() );
        }
    }

    /**
     * Suspends a flow. All marked information will be stored.
     */
    public static void suspendFlow( Flow flow ) throws BrillienException {
        BrillienContext.systemLog( Level.FINE, "Flow suspending:" + flow.getName() );
        if( flows.contains( flow ) )
            flows.remove( flow );

        TransactionServices.closeFlow( (SuperFlow)flow );

        if( flow.isIndispensable() ){
            CouchDBServices.reportFlowSuspense( flow.getFlowID(), flow.getStateSpace() );
        }

        BrillienContext.systemLog( Level.FINE, "Flow suspended::" + flow );
    }

    /**
     * Rolls back a Flow instance in case of exception occurence
     * @param flow flow to be rolled back
     */
    public static void rollbackFlow( Flow flow, String message ) {
        BrillienContext.systemLog( Level.FINE, "Flow rollbacking..." + flow );

        if( flows.contains( flow ) )
            flows.remove( flow );

        TransactionServices.rollbackFlow( (SuperFlow)flow );
        /*
        if( flow.getActivationData() != null )
            CouchDBServices.clearMessageDocumentsByPacketID( flow.getActivationData().getPacketID() );
        */
        if( flow.isIndispensable() )
            CouchDBServices.reportFlowFailure( flow.getFlowID(), message );

        BrillienContext.systemLog( Level.FINE, "Flow rollbacked::" + flow );
    }

    /**
     * Removes a Flow instance
     */
    public static void closeFlow( Flow flow )  {
        BrillienContext.systemLog( Level.FINE, "Flow closing..." + flow );

        if( flows.contains( flow ) )
            flows.remove( flow );

        TransactionServices.closeFlow( (SuperFlow)flow );
        /*
        if( flow.getActivationData() != null )
            CouchDBServices.clearMessageDocumentsByPacketID( flow.getActivationData().getPacketID() );
        */
        if( flow.isIndispensable() )
            CouchDBServices.reportFlowTermination( flow.getFlowID() );

        BrillienContext.systemLog( Level.FINE, "Flow unregistered::" + flow );
    }


}
